home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / System / dir-scanning-sample-c.txt < prev    next >
Encoding:
Internet Message Format  |  1994-03-23  |  18.1 KB  |  [TEXT/EDIT]

  1. Date: Wed, 2 Sep 92 15:19:54 PDT
  2. From: macmod@SUMEX-AIM.Stanford.EDU (Info-Mac Moderator)
  3.  
  4. orrow.stanford.edu!stanford.edu!ames!haven.umd.edu!darwin.sura.net!wup        ost!micro-heart-of-gold.mit.edu!bu.edu!Shiva.COM!world!aep 
  5. From: aep@world.std.com (Andrew E Page) 
  6. Newsgroups: comp.sys.mac.programmer 
  7. Subject: Re: Directory Scan Revisited dirscan.c (C Code) 
  8. Message-Id: <Btyt08.JLL@world.std.com> 
  9. Date: 2 Sep 92 19:04:54 GMT 
  10. References: <BtwK89.oI@world.std.com> 
  11. Organization: The World Public Access UNIX, Brookline, MA 
  12. Lines: 625 
  13. Apparently-To: info-mac 
  14. Resent-To: backmod
  15. Resent-Date: Wed, 2 Sep 1992 15:19:53 PDT
  16. Resent-From: Info-Mac Moderator <macmod@sumex-aim.Stanford.EDU>
  17.  
  18. Here is the directory scanning code.  Written under MPW 3.2.
  19.  
  20. --------CUT HERE------------------------------------------------CUT HERE------
  21.  
  22. #ifdef USE_PRECOMPILE
  23. #pragma load ":Headers:dirscanh.hpc"
  24. #endif
  25.  
  26. #ifndef __ERRORS__
  27. #include <Errors.h>
  28. #endif
  29. #ifndef __FILES__
  30. #include <Files.h>
  31. #endif
  32. #ifndef __MEMORY__
  33. #include <Memory.h>
  34. #endif
  35. #ifndef __TOOLUTILS__
  36. #include <ToolUtils.h>
  37. #endif
  38. #ifndef __DIRSCAN__
  39. #include <dirscanh.h>
  40. #endif
  41.  
  42. #ifdef PRECOMPILE
  43. #undef PRECOMPILE
  44. #pragma dump ":Headers:dirscanh.hpc"
  45. #endif
  46.  
  47. /*
  48.  
  49.   dirscan.c by Andrew E. Page.    Macintosh Directory Scanning Code
  50.   The code operates as follows. 
  51.  
  52.   At the start of the scan StartScan accepts the directory in
  53.   which to start.  It initializes all the structures, starting
  54.   the scan in the directory at index 1, which is the first file
  55.   in the given directory.  For each call to GetNextScanFile the
  56.   file information for the current index is fetched, and the index
  57.   is incremented by one.  The entry is checked to see if it is
  58.   a directory.    If so the current state of the scan, the current
  59.   directory being scanned and the index for the next file, is pushed
  60.   onto a 'stack', and a recursive call to GetNextScanFile is made.    
  61.  
  62.  
  63.   If PBGetCatInfo returns a "File Not Found" error (-43 fnfErr) 
  64.   occurs during the scan the state of the stack is checked.  If
  65.   the stack is 'empty' (n_stackEntries == 0) then the scan is
  66.   at the top of the directory tree and has accessed the last
  67.   file, and the tree is exhasted.  Any other error should cause
  68.   the termination of the scan (this depends on how the code is
  69.   being used at a higher level however).
  70.   If the stack is not empty then the last entry is 'popped' off
  71.   of the stack and scanning resumes at that level.
  72.  
  73.  
  74. Example:
  75.  
  76. Consider the following directory tree.    With the vRefNum for <startdir>
  77. passed to StartScan, the files would be returned in the following
  78. order.    
  79.  
  80. file1a file1b file3a file3b file2a file2b file1c file4a
  81.  
  82.                      <startdir>
  83.                          |
  84.                          |
  85.                          |
  86.   file1a    file1b     <dir1a>   file1c    <dir1b>
  87.                         |                   |
  88.                         |                   |
  89.                         |                   |
  90.               <dir2a> file2a file2b        |
  91.                  |                           |
  92.                  |                           |
  93.                  |                           |
  94.            file3a file3b                   |
  95.                                            |
  96.                                            |
  97.                                         file4a
  98.                     
  99.  
  100.  
  101. */
  102.  
  103.  
  104. typedef struct {
  105.   long    dirID ; // the reference for the directory
  106.   short index ;  // what index it stopped scanning at
  107.   } StackEntry_t ;
  108.  
  109. typedef struct {
  110.   /*
  111.   * these fields provide
  112.   * control over the 'stack'.  Which
  113.   * saves the state of the scan from one
  114.   * level to the next.    
  115.   */
  116.   StackEntry_t    **entries ;       // container for the entries
  117.   long            n_stackEntries ;  // number of entries currently in stack
  118.   long            maxStackEntries ; // maximum entries stack can hold
  119.   long            level ;           // directory level we've decended to
  120.  
  121.   /*
  122.   * Storage space for the Parameter Block record.  This allows
  123.   * us to recycle certain fields without having to constantely
  124.   * reinitialize them.    
  125.   */
  126.   CInfoPBRec    F ;               // CInfo Rec ref IM IV-125
  127.  
  128.   /*
  129.   * filtering parameters
  130.   */
  131.   short       doFiltering ;       // is filtering necessary?
  132.   int          numTypes ;          // number of OSTypes to check for
  133.   OSType      *TypeList ;          // List of the types to check
  134.  
  135.   pascal Boolean (*filter)(FileParam *f) ; // filter function to be called for each file
  136.  
  137.   } ScanControl_t ;
  138.  
  139. /*
  140. *  We start off with a stack with a capacity for 32 entries.  Allowing
  141. *  us to descend 32 levels without having to reallocate any more memory.
  142. *  That should be sufficient for most applications.  However, more
  143. *  memory will be allocated if need be.  Each allocation will only
  144. *  require 192 additional bytes.
  145. *
  146. *  Season to taste here, just so long as STACK_BLOCK_INCREMENT >= 1
  147. */
  148. #define STACK_BLOCK_INCREMENT 32
  149.  
  150. /*
  151. * This is an internal function, hence the static qualifier. 
  152. * This provides the "push" or store function of a standard LIFO(Last In First Out)
  153. * queue.  Otherwise called a 'stack'.
  154. *
  155. *    Each time that it is called it stores the entry, in our case the directory
  156. * ID where the scan is 'branching' from, and the index in that directory where the
  157. * branch is taken, stores them in the container, and then increments the stack
  158. * pointer by one to make room for the next entry.  This differs slightly from the
  159. * nature of the 68XXX stack in as much as for a 68XXX stack the pointer is
  160. * predecremented to make space for the new entry.
  161. *
  162. *     Entries are retrieved from this stack with the PopFromStack function.    
  163. */
  164. static PushToStack(ScanControl_t *SC, long dirID, short index)
  165. {
  166.  
  167.   if( SC->n_stackEntries == SC->maxStackEntries )
  168.     {
  169.     /*
  170.     * Make room for more entries
  171.     */
  172.     SC->maxStackEntries += STACK_BLOCK_INCREMENT ;
  173.     SetHandleSize((Handle)SC->entries, sizeof(StackEntry_t) * SC->maxStackEntries) ;
  174.     }
  175.     
  176.   /*
  177.   * put the entry on the stack
  178.   */
  179.  
  180.   (*(SC->entries))[SC->n_stackEntries].dirID = dirID ;
  181.   (*(SC->entries))[SC->n_stackEntries].index = index ;
  182.  
  183.   SC->n_stackEntries += 1 ;
  184.   SC->level += 1 ;
  185.  
  186. } /* end of PushToStack */
  187.  
  188. /*
  189. * This is an internal function, hence the static qualifier. 
  190. * This provides the "pop" or retrieve function of a standard LIFO(Last In First Out)
  191. * queue.  Otherwise called a 'stack'.
  192. *
  193. *  Each time that it is called it restores the state of the scan from where
  194. * it had 'branched' in descending to another directory level.  It starts by
  195. * decrementing the 'stack pointer' so that it points at the last entry that was
  196. * placed on the stack by the PushToStack function.    
  197. */
  198. static PopFromStack(ScanControl_t *SC, long *dirID, short *index)
  199. {
  200.  
  201.   SC->n_stackEntries -= 1 ; 
  202.   SC->level -= 1 ;
  203.  
  204.   *index = (*(SC->entries))[SC->n_stackEntries].index ;
  205.   *dirID = (*(SC->entries))[SC->n_stackEntries].dirID ;
  206.  
  207. } /* end of PopFromStack */
  208.  
  209. #ifndef FALSE
  210. #define FALSE 0
  211. #define TRUE 1
  212. #endif
  213.  
  214. /*
  215. * This internal function determines whether or not this is a type
  216. * of file that we are looking for.    It first checks the files 'type'
  217. * against the list of types provided by the user, or simply passes it
  218. * on if the numTypes was specified as -1.
  219. *  If it succeeds in passing that check it then executes the 'filter' function
  220. * if supplied by the caller.
  221. */
  222. static Boolean doFilter(ScanControl_t *SC, FileParam *f)
  223. {
  224. int i ;
  225. short isType = FALSE ;
  226.  
  227.   /*
  228.   * check against types
  229.   */
  230.  
  231.   if( SC->numTypes != -1 )
  232.     {
  233.     for( i = 0 ; i < SC->numTypes ; i++ )
  234.       {
  235.       if( SC->TypeList[i] == f->ioFlFndrInfo.fdType )
  236.         {
  237.         isType = TRUE ;
  238.         break ;
  239.         }
  240.       }
  241.     /*
  242.     * Is this one of the file types that we are looking for?
  243.     * if not then exclude it from the list. 
  244.     */
  245.     if( !isType )
  246.       return TRUE ;
  247.     }
  248.  
  249.   /*
  250.   * Perform any additional filtering with the
  251.   * filter proc specified by the caller.
  252.   */
  253.   if( SC->filter )
  254.     return (*SC->filter)(f) ;
  255.  
  256.  
  257.   /*
  258.   * Otherwise if we've reached this point
  259.   * we do not want this filtered from the list
  260.   */
  261.   return FALSE ;
  262.  
  263. }
  264.  
  265.  
  266. /*
  267. *  This is essentially the core starting routine.  It will also be used
  268. * when starting a scan with a WDVRef instead of a ioDirID.    
  269. *
  270. *  Initializes all the fields necessary, setting up the Parameter
  271. * block for subsequent calls to GetNextDirScanFile. 
  272. *
  273. *  Sets up the Stack's controlling data structures, initializing the stack
  274. * pointer to 0 and allocating an initial block of entries.    
  275. *
  276. *  Does some pre-filter checks to optimize for the case where no filtering
  277. * is required.    It copies the TypeList to ensure that the data remains
  278. * valid if this function is being called from a routine where the TypeList
  279. * may reside in the stack frame that may be deallocated before calls to
  280. * GetNextScanFile.    
  281. *
  282. */
  283. Ptr StartDirScanDirID(short volRef, long dirID, int numTypes, OSType *TypeList, pascal Boolean (*filter)(FileParam *f))
  284. {
  285. ScanControl_t *SC ;
  286. StackEntry_t **entries ;
  287. OSType *theTypes ;
  288. long len ;
  289.  
  290.   /*
  291.   * Setup the control structure(s)
  292.   * At each allocation check to ensure that we
  293.   * got the space asked for.  If not, deallocate any
  294.   * previous allocated memory and return a 0L, indictating
  295.   * that the setup failed.    
  296.   */
  297.  
  298.   /*
  299.   * Check to see if the caller has provided a type list.
  300.   * This will be the case if numTypes is > 0.
  301.   * If so copy the list to ensure that it remains valid.
  302.   */
  303.   if( numTypes > 0 )
  304.     {
  305.     /*
  306.     * Make space for the copy of the TypeList.    
  307.     */
  308.     theTypes = (OSType *)NewPtr(len = numTypes * sizeof(OSType)) ; // ref IM I-75
  309.     if( theTypes == 0L )
  310.       return 0L ;
  311.     /*
  312.     * copy the scan types    
  313.     */
  314.     BlockMove(TypeList, theTypes, len) ; // ref IM II-44
  315.     }
  316.   else
  317.     theTypes = 0L ;
  318.     
  319.   /*
  320.   * Allocate space for the stack.  Start off with STACK_BLOCK_INCREMENT
  321.   * entries.  More can be added later if necessary. 
  322.   */
  323.   entries = (StackEntry_t **)NewHandle(sizeof(StackEntry_t) * STACK_BLOCK_INCREMENT) ; // ref IM I-76
  324.   if( entries == 0L )
  325.     {
  326.     DisposPtr((Ptr)theTypes) ; // ref IM I-75
  327.     return 0L ;
  328.     }
  329.  
  330.   /*
  331.   * Allocate space for the overal control structure that
  332.   * will be passed back to the caller.    
  333.   */
  334.   SC = (ScanControl_t *)NewPtr(sizeof(ScanControl_t)) ; // ref IM I-75
  335.   if( SC == 0L )
  336.     {
  337.     DisposPtr((Ptr)theTypes) ; // ref IM I-75
  338.     DisposHandle((Handle)entries) ;
  339.     return 0L ;
  340.     }
  341.     
  342.   /*
  343.   * Setup the filtering
  344.   */
  345.  
  346.   if( (numTypes > 0) || (filter != 0L) )
  347.     {
  348.     SC->doFiltering = TRUE ;
  349.     SC->numTypes = numTypes ;
  350.     SC->TypeList = theTypes ;
  351.     SC->filter = filter ;
  352.     }
  353.   else
  354.     {
  355.     SC->TypeList = 0L ;
  356.     SC->doFiltering = FALSE ;
  357.     }
  358.     
  359.   /*
  360.   * initialize the current
  361.   * state of the scan
  362.   */
  363.  
  364.   SC->entries = entries ;
  365.   SC->n_stackEntries = 0 ;
  366.   SC->maxStackEntries = STACK_BLOCK_INCREMENT ;
  367.   SC->level = 0 ;
  368.  
  369.   /*
  370.   * Setup the initial PB Rec  ref IM IV-125
  371.   */
  372.  
  373.   SC->F.hFileInfo.ioCompletion = 0L ; // always initialize this field!
  374.   SC->F.hFileInfo.ioFlParID = dirID ; // Parent dirtory to start scan in.
  375.   SC->F.hFileInfo.ioVRefNum = volRef ;// true volume reference number
  376.   SC->F.hFileInfo.ioFDirIndex = 0 ;   // initial scanning index.  (will be incremented for each call)
  377.  
  378.   return (Ptr)SC ;
  379.  
  380. } /* end of StartDirScanDirID */
  381.  
  382. /*
  383. *  This call allows us to start off with a WDVRef that may have been
  384. * returned from SFGetFile.    It returns in volRef the true volume
  385. * reference for the volume where the working directory passed in
  386. * vRef resides. 
  387. */
  388. Ptr StartDirScanWDVRef(short vRef, short *volRef, int numTypes, OSType *TypeList, pascal Boolean (*filter)(FileParam *f))
  389. {
  390. OSErr err ;
  391. WDPBRec wd ;
  392.  
  393.   /*
  394.   * Setup the the wd parameter block
  395.   */
  396.  
  397.   wd.ioCompletion = 0L ;
  398.   wd.ioNamePtr = 0L ;
  399.   wd.ioVRefNum = vRef ;
  400.   wd.ioWDIndex = 0 ;
  401.  
  402.   /*
  403.   * obtain the WD info
  404.   */
  405.  
  406.   err = PBGetWDInfo(&wd, FALSE) ;
  407.   if( err )
  408.     return 0L ;
  409.     
  410.   *volRef = wd.ioWDVRefNum ; /* the true volume reference number */
  411.  
  412.   /*
  413.   * use StartDirScanDirID to initialize the scan.
  414.   */
  415.   return StartDirScanDirID(wd.ioWDVRefNum, wd.ioWDDirID, numTypes, TypeList, filter) ;
  416.     
  417. } /* end of StartDirScan */
  418.  
  419.  
  420. /*
  421. * This function is essentially the core of the system.    Each call to this function
  422. * will produce a file, or an fnfErr when the scan is completed.  Any other error is 
  423. * an indication of something else being terribly wrong.
  424. *
  425. *  At each call the function will do the following:
  426. *
  427. *     1.  Increment the index of the file that is currently pointing at.
  428. *
  429. *     2.  Get the file's catalog information with PBGetCatInfo.
  430. *
  431. *     3.  If the file is a directory it will save the state of the scan on the
  432. *         internal stack, and make a recursive call to itself to the next level. 
  433. *
  434. *     4.  If PBGetCatInfo returns with a File Not Find Error (fnfErr -43), it pops
  435. *         the last entry off of the stack, restoring the state of the scan and makes
  436. *         a recursive call to itself to return to the previous level.  If the stack
  437. *         at this point is empty it returns the fnfErr to the caller indicating that
  438. *         the scan has completed.
  439. *
  440. *     5.  If the file entry is not a directory it checks it against the types and
  441. *         filter function specified by the caller.  If it is rejected by the filter, 
  442. *         (when it returns TRUE) GetNextDirScanFile repeats steps 1 through 4.
  443. */
  444. OSErr GetNextDirScanFile(Ptr scanPtr, char *fName, long *dirID, long *level)
  445. {
  446. ScanControl_t *SC = (ScanControl_t *)scanPtr ;
  447. OSErr err ;
  448. short flag = FALSE ;
  449. long parID = SC->F.hFileInfo.ioFlParID ;
  450.  
  451.   SC->F.hFileInfo.ioNamePtr = fName ;
  452.  
  453.   do {
  454.     SC->F.hFileInfo.ioFDirIndex++ ;
  455.     SC->F.hFileInfo.ioDirID = parID ;
  456.     err = PBGetCatInfo(&SC->F, FALSE) ;
  457.     if( err )
  458.       {
  459.       /*
  460.       * Anything other than a fnfErr, or a noErr is an inidcation
  461.       * of some catastrophic failure and that the scan cannot procede.
  462.       */
  463.       if( err != fnfErr )
  464.         return err ;
  465.         
  466.       /*
  467.       * if there is an fnfErr and there are no more stack entries, this means
  468.       * that the tree that is being searched has been exhausted.  Returning
  469.       * the fnfErr at this point should terminate the scan by the caller.
  470.       */
  471.       if( SC->n_stackEntries == 0 )
  472.         return err ;
  473.     
  474.       /*
  475.       * if it is a fnfErr, and entries remain in the stack, we have exhausted
  476.       * the file entries at this level and need to return to the previous level
  477.       * and resuming scanning where  we left off.  To do this we Pop the previous
  478.       * state of the scan  off of the stack, and make a recursive call to GetNextDirScanFile.
  479.       */
  480.       PopFromStack(SC, &SC->F.hFileInfo.ioFlParID, &SC->F.hFileInfo.ioFDirIndex) ;
  481.             
  482.       return GetNextDirScanFile((Ptr)SC, fName, dirID, level) ;
  483.       }
  484.  
  485.     if( SC->F.hFileInfo.ioFlAttrib & 0x10 ) // check for directory flag
  486.       {
  487.       /*
  488.       * Save the state of the scan
  489.       * at the current level.
  490.       */
  491.       PushToStack(SC, SC->F.dirInfo.ioDrParID, SC->F.hFileInfo.ioFDirIndex) ;
  492.     
  493.       /*
  494.       * Setup the the state of the next level to be
  495.       * the directory that we just found.  Starting at the
  496.       * first file index.
  497.       */
  498.       SC->F.hFileInfo.ioFlParID = SC->F.dirInfo.ioDrDirID ;
  499.       SC->F.hFileInfo.ioFDirIndex = 0 ;
  500.     
  501.       return GetNextDirScanFile((Ptr)SC, fName, dirID, level) ;
  502.       }
  503.  
  504.     /*
  505.     * if necessary check to see if we should filter
  506.     * out this file entry.    
  507.     */
  508.     
  509.     if( SC->doFiltering )
  510.       flag = doFilter(SC, (FileParam *)&SC->F) ;
  511.     } while( flag ) ; // if file is to be filtered, try another one.
  512.     
  513.   /*
  514.   * Set the directory ID parameter
  515.   */
  516.   *dirID = SC->F.hFileInfo.ioFlParID ;
  517.  
  518.   /*
  519.   * if the caller wants the level of this file set it.
  520.   */
  521.   if( level )
  522.     *level = SC->level ;
  523.     
  524.   return err ;
  525.  
  526. } /* end of GetNextDirScanFile */
  527.  
  528.  
  529. /*
  530. * This terminates the scan.  What is actually does is release
  531. * all the memory that was allocated.  Since no new working
  532. * directories were created there is no need to call PBCloseWD.
  533. */
  534. OSErr StopDirScan(Ptr scanPtr)
  535. {
  536. ScanControl_t *SC = (ScanControl_t *)scanPtr ;
  537.  
  538.   if( SC->TypeList )
  539.     DisposPtr((Ptr)SC->TypeList) ;
  540.  
  541.   DisposHandle((Handle)SC->entries) ;
  542.   DisposPtr(scanPtr) ;
  543.  
  544.   /*
  545.   * Although it is unlikely that we will have comitted
  546.   * any errors provide this check to the caller.
  547.   */
  548.   return MemError() ;
  549. }
  550.  
  551. /*
  552. *
  553. * This function will generate a full pathname to
  554. * the file identified by its name, volume, and directory ID.
  555. * The result is a handle that contains the full pathname of the
  556. * file and is sized to fit that pathname exactly.  It contains
  557. * No length byte or null terminator.  The length of the string can
  558. * be determined with GetHandleSize. 
  559. *
  560. *
  561. * The function operates as a loop.    At each interation through the
  562. * the loop it gets the catalog name for the entry.    Each time
  563. * making the parent directory for that entry the target for the
  564. * next cycle.  This proceeds until it hits the root directory
  565. * is reached, which has no logical parent, and thus PBGetCatInfo
  566. * returned a file not found error(-43 fnfErr).
  567. *
  568. *     We use a handle since this allows us to use the
  569. * Munger string manipulator.
  570. *
  571. */
  572.  
  573. Handle FullPathNameID(char *fName, int vRef, long dirID)
  574. {
  575. Handle theString = NewHandle(0) ; /* container for the string */
  576. CInfoPBRec F ;
  577. short err ;
  578. char colon = ':' ; /* just in case you want to put it into a stand alone code resource */
  579. char string[64] ;
  580.  
  581.   /*
  582.   * For safety sake(to ensure that our string contains enough space for 
  583.   * subsequent entries found, and to make sure that we don't overwrite
  584.   * the input string, we copy the input fileName to our own storage.
  585.   */
  586.  
  587.   BlockMove(fName, string, fName[0]+1) ; // ref IM II-44
  588.  
  589.   /*
  590.   * Initialize the PB fields
  591.   */
  592.  
  593.   F.hFileInfo.ioNamePtr = string ;    /* pascal string for the name, and also subsequent storage on calls */
  594.   F.hFileInfo.ioVRefNum = vRef ;  /* volume reference (Gotten from SFGetFile perhaps */
  595.   F.hFileInfo.ioCompletion = 0L ; /* initialize this field or get a Serious BOMB */
  596.   F.hFileInfo.ioFDirIndex = 0 ;   /* for the first call only.  Get CatInfo based on ioNamePtr, ioVRefNum, and dirID */
  597.   F.hFileInfo.ioDirID = dirID ;   /* directory containing this file */
  598.  
  599.   while( !(err = PBGetCatInfo(&F,0)) )
  600.     {
  601.     /*
  602.     * Insert the directory level into the front of the string
  603.     */
  604.     Munger(theString, 0, 0L, 0, &string[1], string[0]) ; // ref IM I-468
  605.  
  606.     /*
  607.     * Insert a colon in front of the Directory Entry
  608.     */
  609.     Munger(theString, 0, 0L, 0, &colon, 1) ; // ref IM I-468
  610.  
  611.     /*
  612.     * We now wish to get the information about
  613.     * the directory that contains this file/directory.
  614.     * in short we are moving one level UP in the heirarchy
  615.     */
  616.     F.dirInfo.ioDrDirID = F.dirInfo.ioDrParID ; /* get the ioDrDirID of the PARENT directory (the next level up) */
  617.     
  618.     /*
  619.     * Setting this field to < 0 will
  620.     * cause PBGetCatInfo to get the catalog
  621.     * information based on the VRef and dirID
  622.     * that we've placed in F.dirInfo.ioDrDirID,
  623.     * and return the name of the entry in the
  624.     * pointer to the string in ioNamePtr.
  625.     */
  626.     F.hFileInfo.ioFDirIndex = -1 ;
  627.     }
  628.     
  629.   /*
  630.   * The last entry will be the Volume name which should not have a colon in front of it
  631.   */
  632.     
  633.   Munger(theString, 0, 0L, 1, string, 0) ;
  634.  
  635.   return theString ;
  636. }
  637.  
  638.  
  639. -- 
  640. Andrew E. Page CTO(Warrior Poet)|   Decision and Effort The Archer and Arrow
  641. DSP Ironworks                   |     The difference between what we are
  642. Macintosh and DSP Technology    |           and what we want to be.
  643.  
  644.  
  645.